'''
Created on 16/11/2013

@author: lior
'''

import sexprs

constants = (sexprs.Boolean, sexprs.Char, sexprs.AbstractNumber, sexprs.String, sexprs.Vector, sexprs.Nil)
coreForms = {'IF','LAMBDA','OR','DEFINE','COND','AND','LET','LET*','LETREC','QUOTED','QQUOTED'}


class HF:
    @staticmethod
    def unbind(i,dic):
        if 'E'+ str(i) in dic:
            argi=dic['E'+str(i)].getLeft()
            expi=dic['E'+str(i)].getRight().getLeft()
            args,exps=HF.unbind(i+1,dic)
            return (sexprs.Pair(argi,args),sexprs.Pair(expi,exps))
        else:
            return (sexprs.Nil(),sexprs.Nil())

    @staticmethod
    def pairbody(i,dic):
        if 'E'+str(i) in dic:
            return sexprs.Pair(dic['E'+str(i)],HF.pairbody(i+1,dic))
        else:
            return sexprs.Nil()

    @staticmethod
    def pairToDic(p):
            dic = {}
            i = 0
            while isinstance(p, sexprs.Pair):
                dic['E' + str(i)] = p.getLeft()
                p= p.getRight()
                i += 1
            if not isinstance(p,sexprs.Nil):
                dic['E' + str(i)] = p
            return dic



class NoSuchExp(Exception):
    pass

class AbstractSchemeExpr:
    @staticmethod
    def tagParser(sexprAST):

        '''Parse constants'''
        if isinstance(sexprAST, constants):
            return Constant(sexprAST)


        '''Parse variable'''
        if isinstance(sexprAST, sexprs.Symbol):
            if str(sexprAST) not in coreForms:
                return Variable(sexprAST)
            else:
                return sexprAST


        '''Parse pair'''

        sexprDic=HF.pairToDic(sexprAST)
        parsedE0=AbstractSchemeExpr.tagParser(sexprDic['E0'])

        if isinstance(parsedE0, sexprs.Symbol):

            '''Parse if expression'''
            if str(parsedE0) == 'IF':
                if 'E1' in sexprDic and 'E2' in sexprDic:
                    test=AbstractSchemeExpr.tagParser(sexprDic['E1'])
                    consequence=AbstractSchemeExpr.tagParser(sexprDic['E2'])
                    if isinstance(test, AbstractSchemeExpr) and isinstance(consequence, AbstractSchemeExpr):
                        if 'E4' not in sexprDic:
                            if 'E3' in sexprDic:
                                alternative=AbstractSchemeExpr.tagParser(sexprDic['E3'])
                                if isinstance(alternative, AbstractSchemeExpr):
                                    return IfThenElse(test,consequence,alternative)
                            else:
                                return IfThenElse(test,consequence,Constant(sexprs.Void()))

                        else:
                            raise  NoSuchExp
                    else:
                        raise  NoSuchExp
                else:
                    raise  NoSuchExp




            '''Parse lambda expression'''
            if str(parsedE0) == 'LAMBDA':
                if 'E1' in sexprDic and 'E2' in sexprDic:
                    i=2
                    body=[]
                    if isinstance(sexprDic['E1'],sexprs.Pair):
                        while 'E' + str(i) in  sexprDic:
                            body.append(AbstractSchemeExpr.tagParser(sexprDic['E'+ str(i)]))
                            i=i+1

                        arguments=[]
                        pairItr=sexprDic['E1']

                        if sexprDic['E1'].isProper():
                            '''Lambda Simple'''
                            while not isinstance(pairItr, sexprs.Nil):
                                arguments.append(Variable(pairItr.getLeft()))
                                pairItr= pairItr.getRight()
                            return LambdaSimple(arguments,body)
                        else:
                            '''Lambda Opt'''
                            while isinstance(pairItr, sexprs.Pair):
                                arguments.append(Variable(pairItr.getLeft()))
                                pairItr= pairItr.getRight()
                            arguments.append(Variable(pairItr))

                            if len(arguments)==4:
                                return LambdaOpt(arguments,body)
                            else:
                                raise  NoSuchExp
                    else:
                        '''Lambda Var'''
                        if isinstance(sexprDic['E1'],sexprs.Symbol):
                            return LambdaVar(Variable(sexprDic['E1']),body)
                else:
                    raise  NoSuchExp



            '''Parse or expression'''
            if str(parsedE0) == 'OR':
                i=1
                args=[]
                while 'E' + str(i) in  sexprDic:
                    args.append(AbstractSchemeExpr.tagParser(sexprDic['E'+ str(i)]))
                    i=i+1
                return Or(args)



            '''Parse define expression'''
            if str(parsedE0) == 'DEFINE':
                if 'E1' in sexprDic:
                    if isinstance(sexprDic['E1'],sexprs.Symbol):
                        if 'E2' in sexprDic and 'E3' not in sexprDic:
                            return Def(Variable(sexprDic['E1']), AbstractSchemeExpr.tagParser(sexprDic['E2']))
                        else:
                            raise NoSuchExp
                    elif isinstance(sexprDic['E1'],sexprs.Pair):
                        #def pairbody(i):
                        #    if 'E'+str(i) in sexprDic:
                        #        return sexprs.Pair(sexprDic['E'+str(i)],pairbody(i+1))
                        #    else:
                        #        return sexprs.Nil()

                        var_name=sexprDic['E1'].getLeft()
                        args=sexprDic['E1'].getRight()#thats a pair
                        body=HF.pairbody(2,sexprDic) #pairbody(2)
                        lambdaExp=sexprs.Pair(sexprs.Symbol('lambda'),sexprs.Pair(args,body))
                        defExp=sexprs.Pair(sexprs.Symbol('define'),sexprs.Pair(var_name,sexprs.Pair(lambdaExp,sexprs.Nil())))
                        return AbstractSchemeExpr.tagParser(defExp)
                raise NoSuchExp




            '''Parse cond expression'''
            def condToNestedIf(i):
                Ti=sexprDic['E'+ str(i)].getLeft()
                Ei=sexprDic['E'+ str(i)].getRight().getLeft()
                if 'E' + str(i+1) in  sexprDic:
                    return sexprs.Pair(sexprs.Symbol('if'),sexprs.Pair(Ti,sexprs.Pair(Ei,sexprs.Pair(condToNestedIf(i+1),sexprs.Nil()))))
                else:
                    if isinstance(Ti,sexprs.Symbol) and str(Ti)=='ELSE':
                        return Ei
                    else:
                        return sexprs.Pair(sexprs.Symbol('if'),sexprs.Pair(Ti,sexprs.Pair(Ei,sexprs.Nil())))

            if str(parsedE0) == 'COND':
                if 'E1' in  sexprDic:
                    return AbstractSchemeExpr.tagParser(condToNestedIf(1))



            '''Expand Parse and expression'''
            def andToNestedIf(i):
                exp=sexprDic['E'+ str(i)]
                if 'E' + str(i+1) in sexprDic:
                    return sexprs.Pair(sexprs.Symbol('if'),sexprs.Pair(exp,sexprs.Pair(andToNestedIf(i+1),sexprs.Nil())))
                else:
                    return sexprs.Pair(sexprs.Symbol('if'),sexprs.Pair(exp,sexprs.Pair(exp,sexprs.Pair(sexprs.Boolean(0),sexprs.Nil()))))

            if str(parsedE0) == 'AND':
                if 'E1' in  sexprDic:
                    return AbstractSchemeExpr.tagParser(andToNestedIf(1))




            '''Expand Parse let expression'''
            if str(parsedE0) == 'LET':
                if 'E1' in sexprDic and isinstance(sexprDic['E1'],sexprs.Pair):
                    bindings=HF.pairToDic(sexprDic['E1'])
                    lambdaArgs,applicArgs=HF.unbind(0,bindings)
                    lambdaBody=HF.pairbody(2,sexprDic)
                    lambdaExp=sexprs.Pair(sexprs.Symbol('lambda'),sexprs.Pair(lambdaArgs,lambdaBody))
                    applicExp=sexprs.Pair(lambdaExp,applicArgs)

                    return AbstractSchemeExpr.tagParser(applicExp)



            '''Expand Parse let* expression'''
            if str(parsedE0) == 'LET*':
                if 'E1' in sexprDic and isinstance(sexprDic['E1'],sexprs.Pair):
                    bindings=HF.pairToDic(sexprDic['E1'])
                    letBody=HF.pairbody(2,sexprDic)

                    def letStarToNestedLet(i):
                        bindi=sexprs.Pair(bindings['E' + str(i)],sexprs.Nil())
                        if 'E' + str(i+1) in bindings:
                            return sexprs.Pair(sexprs.Symbol('let'),sexprs.Pair(bindi,sexprs.Pair(letStarToNestedLet(i+1),sexprs.Nil())))
                        else:
                            return sexprs.Pair(sexprs.Symbol('let'),sexprs.Pair(bindi,letBody))
                    return AbstractSchemeExpr.tagParser(letStarToNestedLet(0))



            '''Expand Parse letrec expression'''
            if str(parsedE0) == 'LETREC':
                if 'E1' in sexprDic and isinstance(sexprDic['E1'],sexprs.Pair):
                    bindings=HF.pairToDic(sexprDic['E1'])
                    lambdaArgs=sexprs.Pair(sexprs.Symbol('@blaaaa'),HF.unbind(0,bindings)[0])
                    letBody=HF.pairbody(2,sexprDic)

                    def letRecToApplic(i):
                        if 'E'+str(i) in bindings:
                                lambdaSym=sexprs.Symbol('lambda')
                                lei=bindings['E'+str(i)].getRight().getLeft()
                                lambdai=sexprs.Pair(lambdaSym,sexprs.Pair(lambdaArgs,sexprs.Pair(lei,sexprs.Nil())))
                                return sexprs.Pair(lambdai,letRecToApplic(i+1))
                        else:
                            return sexprs.Nil()

                    firstLambda=sexprs.Pair(sexprs.Symbol('lambda'),sexprs.Pair(lambdaArgs,letBody))
                    applicArgs=sexprs.Pair(firstLambda,letRecToApplic(0))
                    yagApplic=sexprs.Pair(sexprs.Symbol('YAG'),applicArgs)
                    return AbstractSchemeExpr.tagParser(yagApplic)

            '''Expand Parse quote expression'''
            if str(parsedE0) == 'QUOTED':
                if 'E1' in sexprDic:
                    return Constant(sexprAST.right)

            '''Expand Parse quasi expression'''
            if str(parsedE0) == 'QQUOTED':
                def unquote(e):
                    return isinstance(e,sexprs.Pair) and str(e.left)=='UNQUOTED'
                def unquoteSplice(e):
                    return isinstance(e,sexprs.Pair) and str(e.left)=='UNQUOTEDSPLICED'

                p=HF.pairbody(1,sexprDic)
                ans=p
                if unquote(p):
                    ans=Constant(AbstractSchemeExpr.tagParser(p.right))
                elif unquoteSplice(p):
                    raise NoSuchExp #need to write: "unquote-splicing here makes no sense!"
                else:
                    prev=0
                    while isinstance(p,sexprs.Pair):
                        if unquote(p.left):
                            p.left=Constant(AbstractSchemeExpr.tagParser(p.left.right))
                        if unquoteSplice(p.left):
                            AbstractSchemeExpr.tagParser(p.left.right) #to see that its legal
                            parsedList,r=sexprs.AbstractSexpr.readFromString(str(AbstractSchemeExpr.tagParser(p.left.right)))
                            #print(parsedList)
                            if isinstance(parsedList,sexprs.Pair):
                                PLiter = parsedList
                                while isinstance(PLiter.right,sexprs.Pair):
                                    PLiter=PLiter.right
                                PLiter.right=p.right
                                prev.right=parsedList
                            else:
                                raise NoSuchExp #print trying to splice a non list crap
                        prev=p
                        p=p.right
                return Constant(ans)



        else:
            '''Parse applic expression'''
            i=1
            args=[]
            while 'E' + str(i) in sexprDic:
                args.append(AbstractSchemeExpr.tagParser(sexprDic['E'+ str(i)]))
                i=i+1
            return Applic(parsedE0,args)


    @staticmethod
    def parse(s):
        sexprAST, remaining = sexprs.AbstractSexpr.readFromString(s)
        return AbstractSchemeExpr.tagParser(sexprAST)


class Constant(AbstractSchemeExpr):
    def __init__(self, c):
        self.con = c

    def __str__(self):
        return str(self.con)

class Variable(AbstractSchemeExpr):
    def __init__(self, v):
        self.var = v

    def __str__(self):
        return str(self.var)

class IfThenElse(AbstractSchemeExpr):
    def __init__(self, t,c,a):
        self.test =t
        self.consequent=c
        self.alternate=a

    def __str__(self):
        return '(IF ' + str(self.test) + ' ' + str(self.consequent) + ' ' + str(self.alternate) +')'


class AbstractLambda(AbstractSchemeExpr):
    pass

class LambdaSimple(AbstractLambda):
    def __init__(self, a, b):
        self.args = a
        self.body = b

    def __str__(self):
        ans='(LAMBDA ('
        i = 0
        while i < len(self.args) - 1:
            ans = ans + (str(self.args[i])) + ' '
            i += 1
        ans = ans + str(self.args[i]) + ') '

        i = 0
        while i < len(self.body) - 1:
            ans = ans + (str(self.body[i])) + ' '
            i += 1
        return ans + str(self.body[i]) + ')'



class LambdaOpt(AbstractLambda):
    def __init__(self, a, b):
        self.args = a
        self.body = b

    def __str__(self):
        ans='(LAMBDA ('
        i = 0
        while i < len(self.args) - 1:
            ans = ans + (str(self.args[i])) + ' '
            i += 1
        ans = ans + str(self.args[i]) + ') '

        i = 0
        while i < len(self.body) - 1:
            ans = ans + (str(self.body[i])) + ' '
            i += 1
        return ans + str(self.body[i]) + ')'


class LambdaVar(AbstractLambda):
    def __init__(self, a, b):
        self.args = a
        self.body = b

    def __str__(self):
        ans='(LAMBDA ' + str(self.args) + ' '

        i = 0
        while i < len(self.body) - 1:
            ans = ans + (str(self.body[i])) + ' '
            i += 1
        return ans + str(self.body[i]) + ')'


class Applic(AbstractSchemeExpr):
    def __init__(self, app,args):
        self.applic = app
        self.args = args

    def __str__(self):
        ans= '(' + str(self.applic) + ' '
        for arg in self.args:
            ans=ans + str(arg) + ' '
        return ans[:-1] + ')'

class Or(AbstractSchemeExpr):
    def __init__(self, a):
        self.args = a

    def __str__(self):
        ans='(OR '
        for arg in self.args:
            ans=ans + str(arg) + ' '
        return ans + ')'

class Def(AbstractSchemeExpr):
    def __init__(self, v, e):
        self.var = v
        self.exp = e

    def __str__(self):
        return '(DEFINE ' + str(self.var) + ' ' + str(self.exp) + ')'


exp = AbstractSchemeExpr.parse("(letrec ((x 5) (y 7)) e1 e2 e3)")
print(exp)



